﻿/*
 * jPlayer Plugin for jQuery JavaScript Library
 * http://www.jplayer.org
 *
 * Copyright (c) 2009 - 2014 Happyworm Ltd
 * Licensed under the MIT license.
 * http://opensource.org/licenses/MIT
 *
 * Author: Robert M. Hall
 * Date: 20th November 2014
 * Based on JplayerMp4.as with modifications for rtmp
 */

package happyworm.jPlayer
{
	import flash.display.Sprite;

	import flash.media.Video;
	import flash.media.SoundTransform;

	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.Responder;

	import flash.utils.Timer;
	import flash.utils.getTimer;

	import flash.events.NetStatusEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.events.ErrorEvent;
	import flash.events.UncaughtErrorEvent;
	import flash.utils.clearInterval;
	import flash.utils.setInterval;
	import happyworm.jPlayer.ConnectManager;

	public class JplayerRtmp extends Sprite
	{

		public var myVideo:Video = new Video;
		private var myConnection:NetConnection;
		private var myStream:NetStream;

		public var responder:Responder;

		private var streamName:String;
		
		private var connectString:Object;

		private var firstTime:Boolean = true;

		private var myTransform:SoundTransform = new SoundTransform  ;

		public var myStatus:JplayerStatus = new JplayerStatus  ;

		private var ConnMgr:ConnectManager=new ConnectManager();

		private var timeUpdateTimer:Timer = new Timer(250,0);// Matched to HTML event freq
		private var progressTimer:Timer = new Timer(250,0);// Matched to HTML event freq
		private var seekingTimer:Timer = new Timer(100,0);// Internal: How often seeking is checked to see if it is over.

		private var startBuffer:Number = 3;
		private var maxBuffer:Number = 12;

		public function JplayerRtmp(volume:Number)
		{
			myConnection = new NetConnection  ;
			myConnection.client = this;
			

			// Moved the netconnection negotiation into the ConnectManager.as class - not needed for initial connection
			// may need to add eventHandler back in for errors only or just dispatch from the ConnectManager..revisit...
			
			// myConnection.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler);
			// myConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR,securityErrorHandler);
			myVideo.smoothing = true;
			this.addChild(myVideo);

			timeUpdateTimer.addEventListener(TimerEvent.TIMER,timeUpdateHandler);
			progressTimer.addEventListener(TimerEvent.TIMER,progressHandler);
			seekingTimer.addEventListener(TimerEvent.TIMER,seekingHandler);

			myStatus.volume = volume;
			
			addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandler);

			
		}
		
		
        
        private function uncaughtErrorHandler(event:UncaughtErrorEvent):void
        {
			trace("UNCAUGHT ERROR - try loading again");
			
            if (event.error is Error)
            {
                var error:Error = event.error as Error;
				trace(error);
                // do something with the error
            }
            else if (event.error is ErrorEvent)
            {
                var errorEvent:ErrorEvent = event.error as ErrorEvent;
                // do something with the error
				trace(errorEvent);
            }
            else
            {
                // a non-Error, non-ErrorEvent type was thrown and uncaught
            }
			load();
        }
		
		
		
		private function progressUpdates(active:Boolean):void
		{
			if (active)
			{
				progressTimer.start();
			}
			else
			{
				progressTimer.stop();
			}
		}
		
		private function progressHandler(e:TimerEvent):void
		{
			if (myStatus.isLoading)
			{
				if ((getLoadRatio() == 1))
				{// Close as can get to a loadComplete event since client.onPlayStatus only works with FMS
					this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"progressHandler: loadComplete"));
					myStatus.loaded();
					progressUpdates(false);
				}
			}
			progressEvent();
		}
		
		private function progressEvent():void
		{
			// temporarily disabled progress event dispatching - not really needed for rtmp
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"progressEvent:"));
			updateStatusValues();
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PROGRESS,myStatus));
		}
		
		private function timeUpdates(active:Boolean):void
		{
			if (active)
			{
				timeUpdateTimer.start();
			}
			else
			{
				timeUpdateTimer.stop();
			}
		}
		
		private function timeUpdateHandler(e:TimerEvent):void
		{
			timeUpdateEvent();
		}
		
		private function timeUpdateEvent():void
		{
			updateStatusValues();
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_TIMEUPDATE,myStatus));
		}
		private function seeking(active:Boolean):void
		{
			if (active)
			{
				if (! myStatus.isSeeking)
				{
					seekingEvent();
				}
				seekingTimer.start();
			}
			else
			{
				if (myStatus.isSeeking)
				{
					seekedEvent();
				}
				seekingTimer.stop();
			}
		}
		private function seekingHandler(e:TimerEvent):void
		{
			if ((getSeekTimeRatio() <= getLoadRatio()))
			{
				seeking(false);
				if (myStatus.playOnSeek)
				{
					myStatus.playOnSeek = false;// Capture the flag.
					play(myStatus.pausePosition);// Must pass time or the seek time is never set.
				}
				else
				{
					pause(myStatus.pausePosition);// Must pass time or the stream.time is read.
				}
			}
			else if (myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration)
			{
				// Illegal seek time
				seeking(false);
				pause(0);
			}
		}
		private function seekingEvent():void
		{
			myStatus.isSeeking = true;
			updateStatusValues();
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKING,myStatus));
		}
		private function seekedEvent():void
		{
			myStatus.isSeeking = false;
			updateStatusValues();
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKED,myStatus));
		}


		private function netStatusHandler(e:NetStatusEvent):void
		{
			trace(("netStatusHandler: " + e.info.code));
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"netStatusHandler: '" + e.info.code + "'"));
			//trace("BEFORE: bufferTime: "+myStream.bufferTime+" - bufferTimeMax: "+myStream.bufferTimeMax);
			switch (e.info.code)
			{
				case "NetConnection.Connect.Success" :
					// connectStream(); // This method did not do anything sensible anyway.
					// Do not think this case occurs. This was for the static file connection.
					// Which now seems to be handled by the Connection Manager.
					break;
				case "NetStream.Buffer.Full":
				if(connectString.streamTYPE == "LIVE") {
						myStream.bufferTime = startBuffer; 
					} else {
						myStream.bufferTime = maxBuffer; 
					}
					break;
				case "NetStream.Buffer.Flush":
					myStream.bufferTime = startBuffer; 
					break;	
				case "NetStream.Buffer.Empty":
					myStream.bufferTime = startBuffer; 
					break;
				case "NetStream.Seek.Notify":
					myStream.bufferTime = startBuffer; 
					break;
				case "NetStream.Play.Start" :

					if (firstTime) {
						firstTime = false; // Capture flag

						myStatus.loading();
						this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADSTART,myStatus));

						// NB: With MP4 player both audio and video types get connected to myVideo.
						// NB: Had believed it was important for the audio too, otherwise what plays it?
						if(videoBinding) {
							myVideo.attachNetStream(myStream);
						}

						setVolume(myStatus.volume);

						// Really the progress event just needs to be generated once, and should probably happen before now.
						progressUpdates(true);

						// This is an ASSUMPTION! Made it so that the default GUI worked.
						// Hence why this part should be refactored.
						// Lots of commands sequences after setMedia would be corrupted by this assumption.
						// Bascally, we assume that after a setMedia, you will play it.
						// Doing setMedia and pause(15) cause the flag to be set incorrectly and the GUI locks up.
						myStatus.isPlaying = true; // Should be handled elsewhere.
					}

					// Under RTMP, this event code occurs every time the media starts playing and when a new position is seeked to, even when paused.

					// Since under RTMP the event behaviour is quite different, believe a refactor is best here.
					// ie., Under RTMP we should be able to know a lot more info about the stream.

					// See onMetaDataHandler() for other condition, since duration is vital.
					// See onResult() response handler too.
					// Appears to be some duplication between onMetaDataHandler() and onResult(), along with a race between them occuring.

					break;
				case "NetStream.Play.UnpublishNotify":
					myStream.bufferTime = startBuffer; // was 3
				case "NetStream.Play.Stop" :
					myStream.bufferTime = startBuffer; // was 3
					//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"NetStream.Play.Stop: getDuration() - getCurrentTime() = " + (getDuration() - getCurrentTime())));

					// Check if media is at the end (or close) otherwise this was due to download bandwidth stopping playback. ie., Download is not fast enough.
					if (Math.abs((getDuration() - getCurrentTime())) < 150)
					{// Testing found 150ms worked best for M4A files, where playHead(99.9) caused a stuck state due to firing with ~116ms left to play.
						//endedEvent();
					}
					break;
				case "NetStream.Seek.InvalidTime" :
					// Used for capturing invalid set times and clicks on the end of the progress bar.
					endedEvent();
					break;
				case "NetStream.Play.StreamNotFound" :
					myStatus.error();
					// Resets status except the src, and it sets srcError property.;
					this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ERROR,myStatus));
					break;				
			}
			//trace("AFTER: bufferTime: "+myStream.bufferTime+" - bufferTimeMax: "+myStream.bufferTimeMax);
			// "NetStream.Seek.Notify" event code is not very useful. It occurs after every seek(t) command issued and does not appear to wait for the media to be ready.
		}
		private function endedEvent():void
		{
			trace("ENDED STREAM EVENT");
			var wasPlaying:Boolean = myStatus.isPlaying;

			// timeUpdates(false);
			// timeUpdateEvent();
			pause(0);

			if (wasPlaying)
			{
				this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ENDED,myStatus));
			}
		}
		private function securityErrorHandler(event:SecurityErrorEvent):void
		{
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"securityErrorHandler."));
		}
		public function connectStream():void
		{
			trace("CONNECTING");
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"connectStream."));
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_CANPLAY,myStatus));
			
			timeUpdates(true);
			progressUpdates(true);
			//myVideo.attachNetStream(myStream);
			//setVolume(myStatus.volume);
		}

		private function onResult(result:Object):void
		{
			trace("OnResult EVENT FIRED!");
			myStatus.duration = parseFloat(result.toString()) * 1000;
			trace((("The stream length is " + result) + " seconds"));

			if(!myConnection.connected) {
				load();
			} else {
				//this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_CANPLAY,myStatus,"Rockit!"));
			
			//myStatus.loaded();
			//myStatus.isPlaying=true; 
			if (! myStatus.metaDataReady)
			{
				//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: " + myStatus.duration));

				//  Allow multiple onResult Handlers to affect size. As per PR #131 and #98.
				//  myStatus.metaDataReady = true;

				/*var info:Object = new Object();
				info.duration=myStatus.duration
				info.width=undefined;
				info.height=undefined;
				myStatus.metaData = info;
				*/
				if (myStatus.playOnLoad)
				{
					myStatus.playOnLoad = false;// Capture the flag
					if (myStatus.pausePosition > 0)
					{// Important for setMedia followed by play(time).
						play(myStatus.pausePosition);
					}
					else
					{
						play();// Not always sending pausePosition avoids the extra seek(0) for a normal play() command.
					}

				}
				else
				{
					pause(myStatus.pausePosition);// Always send the pausePosition. Important for setMedia() followed by pause(time). Deals with not reading stream.time with setMedia() and play() immediately followed by stop() or pause(0)
				}
				this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADEDMETADATA,myStatus));
			}
			else
			{
				//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: Already read (NO EFFECT)"));
			}
			
			myStream.play(streamName);
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PLAY,myStatus));
			// timeUpdates(false);
			}

		}
		
		private var overRideConnect:Boolean=false;
		public function doneYet():void {
			if(!myConnection.connected) {
				// try again
				ConnMgr.stopAll(true);
				overRideConnect=true;
				trace("Connected: "+myConnection.connected+" - "+myStatus.loadRequired());				
				load();
			}
		}

		private var videoBinding:Boolean=false;
		public function setFile(src:String,videoSupport:Boolean=false):void
		{
			// videoSupport turns on/off video - by default no video, audio only
			videoBinding=videoSupport;
			/* Dont close the stream or netconnection here anymore so we can recycle if host/appname are the same
			if ((myStream != null))
			{
				myStream.close();
				myConnection.close();
			}
			*/
			if(ConnMgr.getNegotiating() == true) {
			    //ConnMgr.stopAll();
				ConnMgr.setNegotiating(false);
			}
			
			myVideo.clear();
			
			progressUpdates(false);
			timeUpdates(false);

			myStatus.reset();
			myStatus.src = src;
			myStatus.srcSet = true;

			firstTime = true;
			
			//myStatus.loaded();
			
			if(src != "") {
				this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_CANPLAY,myStatus));
			}
			
			//timeUpdateEvent();
		}
		
		public function shutDownNcNs():void {
			trace("Connections Closed");
			timeUpdates(false);
			progressUpdates(false);
			myStream.close();
			myConnection.close();
			
			myStatus.reset();
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ENDED,myStatus));
		}

		public function clearFile():void
		{
			if (myStream != null)
			{
				myStream.close();
				// Dont close the netConnection here any longer, as we may recycle it later
				// may need an extra way to close manually if switching media types after an rtmp session - revisit
				// myConnection.close();
				myStatus.reset();
			}
			setFile("");
			myStatus.srcSet = false;
		}

		public function parseRTMPsrcConnect(rtmpSrc:String):Object
		{
			// rtmp://cp76372.live.edgefcs.net/live/Flash1Office@60204
			var appName:String = "";
			var streamFileName:String = "";
			var startIndex:uint = 2 + rtmpSrc.indexOf("//");
			var streamTYPE:String = "recorded";
			var host:String = rtmpSrc.substr(startIndex);
			var port:String = "";
			host = host.substr(0,host.indexOf("/"));
			var endHost:Number = startIndex + host.length + 1;

			// See if there is a host port specified
			if(host.indexOf(":") != -1) {
				port = host.substr(host.indexOf(":")+1);
				host = host.substr(0,host.indexOf(":"));
			}
			
			// Akamai specific live streams
			if (rtmpSrc.lastIndexOf("/live/") != -1)
			{
				trace("LIVE!");
				
				
				appName = rtmpSrc.substring(endHost,rtmpSrc.lastIndexOf("/live/") + 6);
				streamFileName = rtmpSrc.substr(rtmpSrc.lastIndexOf("/live/") + 6);
				streamTYPE="LIVE";
			} else {
				streamTYPE="RECORDED";
				
			}
			
			// Mp3 streams with standard appname/no instance name, mp3: prefix
			if (rtmpSrc.indexOf("mp3:") != -1) {
				appName = rtmpSrc.substring(endHost,rtmpSrc.indexOf("mp3:"));
				streamFileName = rtmpSrc.substr(rtmpSrc.indexOf("mp3:"));
				if ( streamFileName.indexOf("?") != -1 ) {
					var tmp:String = streamFileName.substring(streamFileName.indexOf("?")) ;
					streamFileName = streamFileName.substr(0,streamFileName.indexOf("?")) + encodeURI(tmp) ;
				} else {
					streamFileName = streamFileName.substr(0,streamFileName.length - 4);
				}
			}
			// rtmp://cp83813.edgefcs.net/ondemand/rob_hall/bruce_campbell_oldspice.flv
			
			// Mp4 streams with standard appname/no instance name, mp4: prefix
			if (rtmpSrc.indexOf("mp4:") != -1) {
				appName = rtmpSrc.substring(endHost,rtmpSrc.indexOf("mp4:"));
				streamFileName = rtmpSrc.substr(rtmpSrc.indexOf("mp4:"));
				if ( streamFileName.indexOf("?") != -1 ) {
					var tmpV:String = streamFileName.substring(streamFileName.indexOf("?")) ;
					streamFileName = streamFileName.substr(0,streamFileName.indexOf("?")) + encodeURI(tmpV) ;
				} else {
					streamFileName = streamFileName.substr(0,streamFileName.length - 4);
				}
			}
			
			// .f4v streams with standard appname/no instance name, .flv extension
			if (rtmpSrc.indexOf(".flv") != -1)
			{
			// allow use of ^ in rtmp string to indicate break point for an appname or instance name that
			// contains a / in it where it would require multiple connection attempts or manual configuratiom
			// of the appname/instancename
			var endApp:Number=0;
			if(rtmpSrc.indexOf("^") != -1) {
				endApp=rtmpSrc.indexOf("^");
				rtmpSrc.replace("^", "/");
			} else {
				endApp = rtmpSrc.indexOf("/",endHost);
			}
				appName = rtmpSrc.substring(endHost,endApp) + "/";
				streamFileName = rtmpSrc.substr(endApp+1);
			}
			
			if(port=="") {
				port="MULTI";
			}
			//rtmp, rtmpt, rtmps, rtmpe, rtmpte


			trace(("\n\n*** HOST: " + host));
			trace(("*** PORT: " + port));
			trace(("*** APPNAME: " + appName));
			trace(("*** StreamName: " + streamFileName));

			var streamParts:Object = new Object;
			streamParts.streamTYPE=streamTYPE;
			streamParts.appName = appName;
			streamParts.streamFileName = streamFileName;
			streamParts.hostName = host;
			streamName = streamFileName;
			
			return streamParts;
		}

		public function load():Boolean
		{
			//trace("LOAD: "+myStatus.src);
			if (myStatus.loadRequired() || overRideConnect==true)
			{
				overRideConnect=false;
				myStatus.startingDownload();
				var lastAppName:String;
				var lastHostName:String;
					
				try{
					// we do a try, as these properties might not exist yet
				if(connectString.appName != "" && connectString.appName != undefined) {
					trace("PREVIOUS APP/HOST INFO AVAILABLE");
					lastAppName = connectString.appName;
					lastHostName = connectString.hostName;
					trace("LAST: "+lastAppName,lastHostName);
				} 
				} catch (error:Error) {
					//trace("*** Caught an error condition: "+error);
				}
				
				connectString = parseRTMPsrcConnect(myStatus.src);
				
				
				
				trace("**** LOAD :: CONNECT SOURCE: " +connectString.hostName +" "+ connectString.appName);
				this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_WAITING, myStatus));
				
				if((connectString.appName == lastAppName && connectString.hostName == lastHostName) && (myConnection.connected)) {
					// recycle the netConnection
					trace("RECYCLING NETCONNECTION");
					if ((myStream != null))
					{
						myStream.close();
					}
					connectStream();
					onBWDone(null,myConnection);
				} else {
					// myConnection.connect(connectString.appName);
					trace("NEW NetConnection Negotiation");
					if ((myStream != null))
					{
						myStream.close();
						myConnection.close();
					}
					
					ConnMgr.stopAll(true);
					ConnMgr.negotiateConnect(this,connectString.hostName,connectString.appName);
				}
				
				trace("**** LOAD2 :: CONNECT SOURCE: " +connectString.hostName +" "+ connectString.appName);
				this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_WAITING, myStatus));
				return true;
			}
			else
			{
				return false;
			}
		}
		
		

		public function onFCUnsubscribe(info:Object):void
		{
			trace(("onFCUnSubscribe worked" + info));
		}

		public function onFCSubscribe(info:Object):void
		{
			trace(("onFCSubscribe worked" + info));
		}

		public function onBWDone(info:Object,nc:NetConnection):void
		{
			if(nc.connected) {
			myConnection=nc;
			trace(((("onBWDone " + info) + " :: ") + myStatus.src));

			var customClient:Object = new Object  ;
			customClient.onMetaData = onMetaDataHandler;
			customClient.onPlayStatus = onPlayStatusHandler;// According to the forums and my tests, onPlayStatus only works with FMS (Flash Media Server).

			myStream = null;
			myStream = new NetStream(myConnection);
			myStream.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler);
			myStream.client = customClient;
				if(connectString.streamTYPE == "LIVE") {
					myStream.bufferTime = 3; // was 3
					myStream.bufferTimeMax = 24;
					startBuffer = 3;
					maxBuffer = 12;

				} else {
					myStream.bufferTime = .2; // was 3
					myStream.bufferTimeMax = 0;
					startBuffer = .2;
					maxBuffer = 12;
				}
		

			//streamName="";
			//var connectString:Object = parseRTMPsrcConnect(myStatus.src);
			//streamName=connectString.streamFileName;

			responder = new Responder(onResult);
			myConnection.call("getStreamLength",responder,streamName);
			} else {
				connectStream();
			}

			trace("PLAY SOURCE: "+connectString);

		}

			public function play(time:Number = NaN):Boolean {
			//trace("PLAY: "+time+" - isPlaying: "+myStatus.isPlaying +" - myStatus.isStartingDownload:"+myStatus.isStartingDownload);
			var wasPlaying:Boolean = myStatus.isPlaying;

			if(!isNaN(time) && myStatus.srcSet) {
				if(myStatus.isPlaying) {
					myStream.pause();
					myStatus.isPlaying = false;
				}
				myStatus.pausePosition = time;
			}

			if(myStatus.isStartingDownload) {
				myStatus.playOnLoad = true; // Raise flag, captured in onMetaDataHandler()
				return true;
			} else if(myStatus.loadRequired()) {
				myStatus.playOnLoad = true; // Raise flag, captured in onMetaDataHandler()
				return load();
			} else if((myStatus.isLoading || myStatus.isLoaded) && !myStatus.isPlaying) {
				if(myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration && connectString.streamTYPE != "LIVE") { // The time is invalid, ie., past the end.
					myStream.pause(); // Since it is playing by default at this point.
					myStatus.pausePosition = 0;
					trace("SEEKER!");
					myStream.seek(0);
					timeUpdates(false);
					timeUpdateEvent();
					if(wasPlaying) { // For when playing and then get a play(huge)
						this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE, myStatus));
					}
				} else if(getSeekTimeRatio() > getLoadRatio()) { // Use an estimate based on the downloaded amount
					myStatus.playOnSeek = true;
					seeking(true);
					trace("SEEKER PAUSE!");
					myStream.pause(); // Since it is playing by default at this point.
				} else {
					if(!isNaN(time)) { // Avoid using seek() when it is already correct.
						trace("SEEKER3");
						myStream.seek(myStatus.pausePosition/1000); // Since time is in ms and seek() takes seconds
					}
					myStatus.isPlaying = true; // Set immediately before playing. Could affects events.
					trace("SHOULD GET RESUME!");
					myStream.resume();
					timeUpdates(true);
					if(!wasPlaying) {
						this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PLAY, myStatus));
					}
				}
				return true;
			} else {
				return false;
			}
		}
		
	public function pause(time:Number=NaN):Boolean
	{
		//trace("PAUSE: "+time);
		myStatus.playOnLoad = false;// Reset flag in case load/play issued immediately before this command, ie., before onMetadata() event.
		myStatus.playOnSeek = false;// Reset flag in case play(time) issued before the command and is still seeking to time set.

		var wasPlaying:Boolean = myStatus.isPlaying;

		
		// To avoid possible loops with timeupdate and pause(time). A pause() does not have the problem.
		var alreadyPausedAtTime:Boolean = false;
		if(!isNaN(time) && myStatus.pausePosition == time) {
			alreadyPausedAtTime = true;
		}
		
		trace("!isNaN: "+!isNaN(time) +" isNaN: "+isNaN(time));

		// Need to wait for metadata to load before ever issuing a pause. The metadata handler will call this function if needed, when ready.
		if (((myStream != null) && myStatus.metaDataReady))
		{// myStream is a null until the 1st media is loaded. ie., The 1st ever setMedia being followed by a pause() or pause(t).
			
			if(connectString.streamTYPE == "LIVE") {
				trace("PAUSING LIVE");
				myStream.play(false) 
			} else {
				trace("PAUSING RECORDED");
			myStream.pause();
			}
		}
		if (myStatus.isPlaying)
		{
			myStatus.isPlaying = false;
			myStatus.pausePosition = myStream.time * 1000;
		}

		if (! isNaN(time) && myStatus.srcSet)
		{
			myStatus.pausePosition = time;
		}

		if (wasPlaying)
		{
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE,myStatus));
		}

		if (myStatus.isStartingDownload)
		{
			return true;
		}
		else if (myStatus.loadRequired())
		{
			if ((time > 0))
			{// We do not want the stop() command, which does pause(0), causing a load operation.
				return load();
			}
			else
			{
				return true;// Technically the pause(0) succeeded. ie., It did nothing, since nothing was required.
			}
		}
		else if (myStatus.isLoading || myStatus.isLoaded)
		{
			if (myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration && connectString.streamTYPE != "LIVE" )
			{// The time is invalid, ie., past the end.
				myStatus.pausePosition = 0;
				
				trace("GOT HERE!");
				myStream.seek(0);
				seekedEvent();// Deals with seeking effect when using setMedia() then pause(huge). NB: There is no preceeding seeking event.
			}
			else if (! isNaN(time))
			{
				if ((getSeekTimeRatio() > getLoadRatio()))
				{// Use an estimate based on the downloaded amount
					seeking(true);
				}
				else
				{
					if (myStatus.metaDataReady && connectString.streamTYPE != "LIVE")
					{// Otherwise seek(0) will stop the metadata loading.
					trace("GOT HERE TOO!");
						myStream.seek(myStatus.pausePosition / 1000);
					}
				}
			}
			timeUpdates(false);
			// Need to be careful with timeupdate event, otherwise a pause in a timeupdate event can cause a loop.
			// Neither pause() nor pause(time) will cause a timeupdate loop.
			if(wasPlaying || !isNaN(time) && !alreadyPausedAtTime) {
				timeUpdateEvent();
			}
			return true;
		}
		else
		{
			return false;
		}
	}
	public function playHead(percent:Number):Boolean
	{
		var time:Number = percent * getDuration() * getLoadRatio() / 100;
		if (myStatus.isPlaying || myStatus.playOnLoad || myStatus.playOnSeek)
		{
			return play(time);
		}
		else
		{
			return pause(time);
		}
	}
	public function setVolume(v:Number):void
	{
		myStatus.volume = v;
		myTransform.volume = v;
		if ((myStream != null))
		{
			myStream.soundTransform = myTransform;
		}
	}
	private function updateStatusValues():void
	{
		//myStatus.seekPercent = 100 * getLoadRatio();
		myStatus.seekPercent = 100;
		myStatus.currentTime = getCurrentTime();
		myStatus.currentPercentRelative = 100 * getCurrentRatioRel();
		myStatus.currentPercentAbsolute = 100 * getCurrentRatioAbs();
		myStatus.duration = getDuration();
	}
	public function getLoadRatio():Number
	{
		return 1;
		/*trace("LoadRatio:"+myStream.bytesLoaded, myStream.bytesTotal);
		if((myStatus.isLoading || myStatus.isLoaded) && myStream.bytesTotal > 0) {
			return myStream.bytesLoaded / myStream.bytesTotal;
		} else if (myStatus.isLoaded && myStream.bytesLoaded > 0) {
			return 1;
		} else {
			return 0;
		}
		*/

	}
	public function getDuration():Number
	{
		return myStatus.duration;// Set from meta data.
	}
	public function getCurrentTime():Number
	{
		if (myStatus.isPlaying)
		{
			//trace(myStream.time * 1000);
			return myStream.time * 1000; // was +1000
		}
		else
		{
			return myStatus.pausePosition;
		}
	}
	public function getCurrentRatioRel():Number
	{

		if ((getCurrentRatioAbs() <= getLoadRatio()))
		{
			//if((getLoadRatio() > 0) && (getCurrentRatioAbs() <= getLoadRatio())) {
			return getCurrentRatioAbs() / getLoadRatio();
		}
		else
		{
			return 0;
		}
	}
	public function getCurrentRatioAbs():Number
	{
		if ((getDuration() > 0))
		{
			return getCurrentTime() / getDuration();
		}
		else
		{
			return 0;
		}
	}
	public function getSeekTimeRatio():Number
	{
		if ((getDuration() > 0))
		{
			return myStatus.pausePosition / getDuration();
		}
		else
		{
			return 1;
		}
	}
	public function onPlayStatusHandler(infoObject:Object):void
	{
		trace((("OnPlayStatusHandler called: (" + getTimer()) + " ms)"));
		for (var prop:* in infoObject)
		{
			trace(((("\t" + prop) + ":\t") + infoObject[prop]));
		}
		if (infoObject.code == "NetStream.Play.Complete")
		{
			endedEvent();
		}
	}

	public function onMetaDataHandler(info:Object):void
	{// Used in connectStream() in myStream.client object.
		// This event occurs when jumping to the start of static files! ie., seek(0) will cause this event to occur.

		if (! myStatus.metaDataReady)
		{
			trace("\n\n*** METADATA FIRED! ***\n\n");
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: " + info.duration + " | " + info.width + "x" + info.height));

			myStatus.metaDataReady = true;// Set flag so that this event only effects jPlayer the 1st time.
			myStatus.metaData = info;
			myStatus.duration = info.duration * 1000;// Only available via Meta Data.
			if (info.width != undefined)
			{
				myVideo.width = myStatus.videoWidth = info.width;
			}
			if (info.height != undefined)
			{
				myVideo.height = myStatus.videoHeight = info.height;
			}

			if (myStatus.playOnLoad)
			{
				myStatus.playOnLoad = false;// Capture the flag
				if (myStatus.pausePosition > 0)
				{// Important for setMedia followed by play(time).
					play(myStatus.pausePosition);
				}
				else
				{
					play();// Not always sending pausePosition avoids the extra seek(0) for a normal play() command.
				}
			}
			else
			{
				pause(myStatus.pausePosition);// Always send the pausePosition. Important for setMedia() followed by pause(time). Deals with not reading stream.time with setMedia() and play() immediately followed by stop() or pause(0)
			}
			this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADEDMETADATA,myStatus));
		}
		else
		{
			//this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG,myStatus,"onMetaDataHandler: Already read (NO EFFECT)"));
		}
	}
}
}
